package com.cooqe.collection_ping.utils;

import com.cooqe.collection_ping.model.Target;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.SystemUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 折戟沉沙铁未销
 * @version V1.0
 * @date 2023/11/14-2023
 * @Description: snmp util 工具类
 */
@Slf4j
@SuppressWarnings("all")
public class PingUtil {

    public static final Pattern LINUX_SUMMARY = Pattern.compile("(\\d+)%(?: min/avg/max/mdev = (\\S+)/(\\S+)/(\\S+)/(\\S+))?");
    public static final Pattern WINDOWS_ALIVE = Pattern.compile("Reply\\[\\d+\\] from (\\S+): bytes=\\d+ time=(\\S+) ms TTL=\\d+");
    public static final int INTERVAL = 10;


    /**
     * 不同的操作系统，ping 命令参数有部分不同。根据当前环境的操作系统，执行不同的ping 命令
     *
     * @param targets
     */
    public static void executePing(List<Target> targets) {
        File directory = new File(System.getProperty("user.dir") + File.separator + "tools");
        String fileName = "fping";
        String archSuffix = "x32";
        if (SystemUtils.OS_ARCH.contains("64")) {
            archSuffix = "x64";
        }
        if (SystemUtils.IS_OS_WINDOWS) {
            fileName += "_win_" + archSuffix + ".exe";
        } else {
            fileName += "_linux_" + archSuffix;
        }
        File fping = new File(directory, fileName);
        if (!fping.canExecute()){
            log.error("无法执行,检查文件是否存、可执行权限!!!");
        }
        if (SystemUtils.IS_OS_WINDOWS) {
            pingWin(targets, fping);
            return;
        }
        pingLinux(targets,fping);
    }


    /**
     * win 系统 执行ping 操作
     *
     * @param targets
     * @return
     */
    public static List<Target> pingWin(List<Target> targets, File fping) {
        //初始化变量
        int retry = 0;
        int timeout = 0;
        Map<Target, String> handleMap = new HashMap<>();
        Map<String, Target> targetMap = new HashMap<>();
        Map<String, ArrayList<Double>> dataMap = new HashMap<>();
        ArrayList<String> ipList = new ArrayList<>();
        for (Target target : targets) {
            ipList.add(target.getIp());
            dataMap.put(target.getIp(), new ArrayList<Double>());
            retry = Math.max(target.getRetry(), retry);
            targetMap.put(target.getIp(), target);
        }

        //定义命令参数
        ArrayList<String> commandList = new ArrayList<>();
        commandList.add(fping.toString());
        commandList.addAll(ipList);
        commandList.add("-t");
        commandList.add(String.valueOf(INTERVAL));
        commandList.add("-n");
        commandList.add(String.valueOf(retry));
        commandList.add("-w");
        commandList.add(String.valueOf(timeout));
        commandList.add("-p");
        commandList.add("-l");
        Process process = null;

        try {
            //执行 ping 操作
            process = new ProcessBuilder(commandList).redirectErrorStream(true).start();
            InputStream in = process.getInputStream();

            //创建单独的一个线程读取ping 返回的结果
            CountDownLatch countDownLatch = new CountDownLatch(1);
            Thread readerThread = new Thread("Win-Fping-Reader") {
                @Override
                public void run() {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                    String line;
                    try {
                        while (!isInterrupted() && (line = reader.readLine()) != null) {
                            Matcher matcher = WINDOWS_ALIVE.matcher(line);
                            if (matcher.find()) {
                                try {
                                    ArrayList<Double> data = dataMap.get(matcher.group(1));
                                    if (data != null) {
                                        data.add(Double.valueOf(matcher.group(2)));
                                    }
                                } catch (Exception e) {
                                    log.error(e.getMessage());
                                }
                            }
                        }
                    } catch (IOException e) {
                        log.error(e.getMessage());
                    } finally {
                        countDownLatch.countDown();
                    }
                }
            };
            //启动线程
            readerThread.start();
            //主线程等待 60 秒
            //countDownLatch.await(60, TimeUnit.SECONDS);
            countDownLatch.await();
            readerThread.interrupt();
            process.destroy();

            //主线程 解析计算数据
            for (Map.Entry<String, ArrayList<Double>> entry : dataMap.entrySet()) {
                String ip = entry.getKey();
                ArrayList<Double> rawDataList = entry.getValue();
                Target target = targetMap.get(ip);
                if (rawDataList.isEmpty()) {
                    target.decreaseHealth();
                } else {
                    double sum = 0;
                    int count = rawDataList.size();
                    for (double rawData : rawDataList) {
                        sum += rawData;
                    }
                    double avg = sum / count;
                    double jitter = 0;
                    for (double rawData : rawDataList) {
                        jitter += Math.pow(rawData - avg, 2);
                    }
                    jitter = Math.pow(jitter / count, 0.5);
                    target.restoreHealth();
                    target.setLatency((int) avg);
                    target.setPacketLoss((target.getRetry() - count) * 100 / count);
                    target.setJitter((int) jitter);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            return targets;
        }
    }


    /**
     * linux 执行ping 操作
     *
     * @param targets
     * @return
     */
    public static List<Target> pingLinux(List<Target> targets, File fping) {
        //初始化变量
        int retry = 0;
        int timeout = 0;
        ArrayList<String> ipList = new ArrayList<>();
        HashMap<String, Target> targetMap = new HashMap<>();
        for (Target target : targets) {
            targetMap.put(target.getIp(), target);
            ipList.add(target.getIp());
            retry = Math.max(target.getRetry(), retry);
            timeout = Math.max(target.getTimeout(), timeout);
        }

        //定义命令参数
        ArrayList<String> commandList = new ArrayList<>();
        commandList.add(fping.toString());
        commandList.addAll(ipList);
        commandList.add("-i");
        commandList.add(String.valueOf(INTERVAL));
        commandList.add("-p");
        commandList.add(String.valueOf(INTERVAL));
        commandList.add("-c");
        commandList.add(String.valueOf(retry));
        commandList.add("-t");
        commandList.add(String.valueOf(timeout));
        commandList.add("-q");
        Process process = null;

        try {
            process = new ProcessBuilder(commandList).redirectErrorStream(true).start();
            InputStream in = process.getInputStream();

            //创建单独的一个线程读取ping 返回的结果
            CountDownLatch countDownLatch = new CountDownLatch(1);
            Thread readerThread = new Thread("Linux-Fping-Reader"){
                @Override
                public void run() {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                    String line;
                    try {
                        while ((line = reader.readLine()) != null) {
                            Matcher matcher = LINUX_SUMMARY.matcher(line);
                            if (matcher.find()) {
                                try {
                                    Target target = targetMap.get(matcher.group(1));
                                    if (target != null) {
                                        target.setLastExecuteTime(System.currentTimeMillis());
                                        int packetLoss = Integer.valueOf(matcher.group(2));
                                        if (packetLoss == 100) {
                                            target.decreaseHealth();
                                        } else {
                                            double min = Double.valueOf(matcher.group(3));
                                            double avg = Double.valueOf(matcher.group(4));
                                            double max = Double.valueOf(matcher.group(5));
                                            target.restoreHealth();
                                            target.setPacketLoss(packetLoss);
                                            target.setLatency((int) avg);
                                            target.setJitter((int) Math.sqrt((Math.pow(avg - min, 2) + Math.pow(max - avg, 2)) / 2));
                                        }
                                    }
                                } catch (Exception e) {
                                    log.error(e.getMessage());
                                }
                            }
                        }
                    } catch (Exception e) {
                        log.error(e.getMessage());
                    } finally {
                        countDownLatch.countDown();
                    }
                }
            };

            //启动线程
            readerThread.start();
            //主线程等待 60 秒
            //countDownLatch.await(60, TimeUnit.SECONDS);
            countDownLatch.await();
            readerThread.interrupt();
            process.destroy();

        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            return targets;
        }
    }

    public static void main(String[] args) {
        Target target1 = new Target("192.168.1.30", 3000, 5);
        Target target2 = new Target("192.168.1.1", 3000, 5);
        Target target3 = new Target("192.168.1.27", 3000, 5);
        Target target4 = new Target("192.168.1.88", 3000, 5);
        List<Target> list=new ArrayList<>();
        list.add(target1);
        list.add(target2);
        list.add(target3);
        list.add(target4);
        executePing(list);
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }


}